/*
Copyright (C) 1996-1997 Id Software, Inc.

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 3
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

*/

// support routines for surface drawing

#include "quakedef.h"

#include <smmintrin.h>
#include <xmmintrin.h>

// space for up to 1,000,000 indexes before we need to discard the buffer
#define MAX_SURF_INDEXES 0x100000

ID3D10Buffer *d3d_SurfIndexes = NULL;
int d3d_FirstSurfIndex = 0;

class CBrushHandler : public CD3DHandler
{
public:
	void OnCreateDevice (void)
	{
		D3D10_BUFFER_DESC ibDesc = {
			MAX_SURF_INDEXES * sizeof (unsigned int),
			D3D10_USAGE_DYNAMIC,
			D3D10_BIND_INDEX_BUFFER,
			D3D10_CPU_ACCESS_WRITE,
			0
		};

		d3d_Device->CreateBuffer (&ibDesc, NULL, &d3d_SurfIndexes);
	}

	void OnReleaseDevice (void)
	{
		SAFE_RELEASE (d3d_SurfIndexes);
	}
} d3d_BrushHandler;


void R_DrawNewTextureChain (texturechain_t *Chain, chaintype_t Type)
{
	if (!Chain->TextureChain) return;
	if (!Chain->NumIndexes) return;

	// choose the correct mapping type
	D3D10_MAP MapType = D3D10_MAP_WRITE_NO_OVERWRITE;

	// even tho these are unsigned int indexes we'll use int * here so that we can use _mm_stream_si32 to write them
	int *ndx = NULL;

	if (d3d_FirstSurfIndex + Chain->NumIndexes >= MAX_SURF_INDEXES)
	{
		MapType = D3D10_MAP_WRITE_DISCARD;
		d3d_FirstSurfIndex = 0;
	}

	if (SUCCEEDED (d3d_SurfIndexes->Map (MapType, 0, (void **) &ndx)))
	{
		// offset to the start of the writable region
		ndx += d3d_FirstSurfIndex;

		// write in surfaces
		for (msurface_t *surf = Chain->TextureChain; surf; surf = surf->NewTextureChain[Type])
		{
			// write in indexes
			for (int v = 2; v < surf->numedges; v++, ndx += 3)
			{
				// don't pollute the cache while streaming indexes at runtime
				_mm_stream_si32 (&ndx[0], surf->firstvert);
				_mm_stream_si32 (&ndx[1], surf->firstvert + (v - 1));
				_mm_stream_si32 (&ndx[2], surf->firstvert + v);
			}
		}

		// unmap and draw it
		d3d_SurfIndexes->Unmap ();
		d3d_Device->DrawIndexed (Chain->NumIndexes, d3d_FirstSurfIndex, 0);

		// advance
		d3d_FirstSurfIndex += Chain->NumIndexes;
		c_draw_call++;
	}

	R_ClearTextureChain (Chain);
}


void R_DrawNewTextureChain (texture_t *tex, chaintype_t Type)
{
	R_DrawNewTextureChain (&tex->TextureChain[Type], Type);
}


void R_AddSurfaceToTextureChain (msurface_t *surf, texturechain_t *Chain, chaintype_t Type)
{
	*Chain->TextureChainTail = surf;
	Chain->TextureChainTail = &surf->NewTextureChain[Type];
	surf->NewTextureChain[Type] = NULL;

	Chain->NumIndexes += surf->numindexes;
}


void R_AddSurfaceToTextureChain (msurface_t *surf, chaintype_t Type)
{
	R_AddSurfaceToTextureChain (surf, &surf->texinfo->texture->TextureChain[Type], Type);
}


void R_ClearTextureChain (texturechain_t *Chain)
{
	Chain->TextureChain = NULL;
	Chain->TextureChainTail = &Chain->TextureChain;
	Chain->NumIndexes = 0;
}


void R_CreateTextureChains (model_t *m)
{
	for (int i = 0; i < m->numtextures; i++)
	{
		texture_t *tex = m->textures[i];

		if (!tex) continue;

		// clear chain types used by this texture
		for (int c = 0; c < MAX_CHAIN_TYPES; c++)
			R_ClearTextureChain (&tex->TextureChain[c]);
	}

	R_ClearTextureChain (&m->SkyChain);
	R_ClearTextureChain (&m->ZFillChain);
}


void R_AddSurfaceStyle (entity_t *e, msurface_t *surf, chaintype_t Type, int flag)
{
	R_AddSurfaceToTextureChain (surf, Type);
	e->drawflags |= flag;
}


void R_AddSurface (msurface_t *surf, texture_t *tex, entity_t *e, float entalpha)
{
	c_brush_polys++;

	// check for alpha and push it off to the alpha drawer if present
	if ((surf->flags & SURF_DRAWTURB) && r_wateralpha->value < 1.0f)
	{
		// add to the list of surfaces for water alpha
		R_AddAlphaSurface (e, surf, r_wateralpha->value);
	}
	else if (!(surf->flags & SURF_DRAWSKY) && entalpha < 1.0f)
	{
		// add to the list of surfaces for solid alpha
		R_AddAlphaSurface (e, surf, entalpha);
	}
	else if (r_fullbright->boolean)
	{
		R_AddSurfaceToTextureChain (surf, CHAIN_FULLBRIGHT);
		e->drawflags |= SURF_DRAWFULLBRIGHT;
	}
	else if (r_realtime_lighting->boolean)
	{
		// save extra surfaces out for later drawing
		if (surf->flags & SURF_DRAWSKY)
			R_AddExtraSurface (surf, e, tex, CHAIN_SKY);
		else if (surf->flags & SURF_DRAWTURB)
			R_AddExtraSurface (surf, e, tex, CHAIN_WATER);
		else
		{
			// save out for later drawing but add it to the RT light chain for it's regular texture too
			if (tex->Animated[1]) R_AddExtraSurface (surf, e, tex, CHAIN_LUMA);

			R_AddSurfaceToTextureChain (surf, CHAIN_DEFERRED);
			e->drawflags |= SURF_DRAWDEFERRED;
		}
	}
	else if (surf->flags & SURF_DRAWSKY)
	{
		R_AddSurfaceToTextureChain (surf, &e->model->SkyChain, CHAIN_SKY);
		e->drawflags |= SURF_DRAWSKY;
	}
	else if (surf->flags & SURF_DRAWTURB)
	{
		R_AddSurfaceToTextureChain (surf, CHAIN_WATER);
		e->drawflags |= SURF_DRAWTURB;
	}
	else
	{
		// check for luma as an additional pass
		if (tex->Animated[1])
		{
			R_AddSurfaceToTextureChain (surf, CHAIN_LUMA);
			e->drawflags |= SURF_DRAWLUMA;
		}

		// add it to the appropriate style chain
		if (surf->NumStyles > 3)
		{
			R_AddSurfaceToTextureChain (surf, CHAIN_LIGHTSTYLE3);
			e->drawflags |= SURF_DRAWLIGHTSTYLE3;
		}
		else if (surf->NumStyles > 2)
		{
			R_AddSurfaceToTextureChain (surf, CHAIN_LIGHTSTYLE2);
			e->drawflags |= SURF_DRAWLIGHTSTYLE2;
		}
		else if (surf->NumStyles > 1)
		{
			R_AddSurfaceToTextureChain (surf, CHAIN_LIGHTSTYLE1);
			e->drawflags |= SURF_DRAWLIGHTSTYLE1;
		}
		else if (surf->NumStyles > 0)
		{
			R_AddSurfaceToTextureChain (surf, CHAIN_LIGHTSTYLE0);
			e->drawflags |= SURF_DRAWLIGHTSTYLE0;
		}
	}
}


void R_NewDrawTextureChains (entity_t *e)
{
	// nothing to draw here
	if (!e->drawflags) return;

	model_t *clmodel = e->model;

	// signify that we're going to a new brush pass
	ref.BrushPasses++;

	// set up the basic draw state for surfaces
	R_SetupSurfaceDrawState (e, e->model, 1.0f);

	if (e->drawflags & SURF_DRAWFULLBRIGHT) R_DrawUnlitSurfaces (e->model, 0, CHAIN_FULLBRIGHT, d3d_BlendNone, d3d_ZmodeNormal);

	// draw all surface types that were chained
	if (e->drawflags & SURF_DRAWZFILL)
	{
		// none of the other flags should be set if we're doing a z-fill pass
		assert ((e->drawflags & ~SURF_DRAWZFILL) == 0);

		// now do the z-fill pass
		R_DrawZFillSurfaces (&e->model->ZFillChain);
	}
	else
	{
		if (e->drawflags & SURF_DRAWDEFERRED) R_DrawDeferredSurfaces (e->model);

		if (e->drawflags & SURF_DRAWLIGHTSTYLE0) R_DrawLightmappedSurfaces (e->model, CHAIN_LIGHTSTYLE0, 0, d3d_BlendNone, d3d_ZmodeNormal);
		if (e->drawflags & SURF_DRAWLIGHTSTYLE1) R_DrawLightmappedSurfaces (e->model, CHAIN_LIGHTSTYLE1, 1, d3d_BlendNone, d3d_ZmodeNormal);
		if (e->drawflags & SURF_DRAWLIGHTSTYLE2) R_DrawLightmappedSurfaces (e->model, CHAIN_LIGHTSTYLE2, 2, d3d_BlendNone, d3d_ZmodeNormal);
		if (e->drawflags & SURF_DRAWLIGHTSTYLE3) R_DrawLightmappedSurfaces (e->model, CHAIN_LIGHTSTYLE3, 3, d3d_BlendNone, d3d_ZmodeNormal);

		// omit dynamics if we're running the realtime pass because that pass runs through this function
		// to pick up extra surfaces
		if (!r_realtime_lighting->boolean) R_DrawDynamicLights (e);

		// draw luma surfaces last so that we can use a max blend on them
		if (e->drawflags & SURF_DRAWLUMA) R_DrawUnlitSurfaces (e->model, 1, CHAIN_LUMA, d3d_BlendMax, d3d_ZmodeNowrite);

		// add turb and sky here too for all draw types
		if (e->drawflags & SURF_DRAWTURB) R_DrawWaterSurfaces (e->model);
		if (e->drawflags & SURF_DRAWSKY) R_DrawSkyChain (&clmodel->SkyChain);
	}

	// clear the drawflags for the entity
	e->drawflags = 0;
}


struct extrasurface_t
{
	msurface_t *surf;
	entity_t *ent;
	texture_t *tex;
	chaintype_t type;
};

#define MAX_EXTRA_SURFACES 65536
extrasurface_t *r_extrasurfaces = NULL;
int r_numextrasurfaces;


void R_ClearExtraSurfaces (void)
{
	// if rt lighting is not used any attempt to access r_extrasurfaces is an error
	if (r_realtime_lighting->boolean)
		r_extrasurfaces = (extrasurface_t *) qScratch->Alloc (MAX_EXTRA_SURFACES * sizeof (extrasurface_t));
	else r_extrasurfaces = NULL;

	r_numextrasurfaces = 0;
}


void R_AddExtraSurface (msurface_t *surf, entity_t *ent, texture_t *tex, chaintype_t type)
{
	if (!r_extrasurfaces) return;

	if (r_numextrasurfaces < MAX_EXTRA_SURFACES)
	{
		r_extrasurfaces[r_numextrasurfaces].surf = surf;
		r_extrasurfaces[r_numextrasurfaces].ent = ent;
		r_extrasurfaces[r_numextrasurfaces].tex = tex;
		r_extrasurfaces[r_numextrasurfaces].type = type;
		r_numextrasurfaces++;
	}
}


void R_DrawExtraSurfaces (entity_t *lastent, entity_t *nextent)
{
	// cool :)
	if (lastent && lastent->drawflags)
	{
		// cut down version of R_NewDrawTextureChains that skips stuff we're not interested in
		model_t *clmodel = lastent->model;

		// signify that we're going to a new brush pass
		ref.BrushPasses++;

		// set up the basic draw state for surfaces
		R_SetupSurfaceDrawState (lastent, lastent->model, 1.0f);

		// draw luma surfaces last so that we can use a max blend on them
		if (lastent->drawflags & SURF_DRAWLUMA) R_DrawUnlitSurfaces (lastent->model, 1, CHAIN_LUMA, d3d_BlendMax, d3d_ZmodeNowrite);

		// add turb and sky here too for all draw types
		if (lastent->drawflags & SURF_DRAWTURB) R_DrawWaterSurfaces (lastent->model);
		if (lastent->drawflags & SURF_DRAWSKY) R_DrawSkyChain (&clmodel->SkyChain);

		lastent->drawflags = 0;
	}

	if (nextent)
	{
		// chains belonging to the model should also be cleared here too
		R_ClearTextureChain (&nextent->model->SkyChain);
		nextent->drawflags = 0;
	}
}


void R_HandleExtraSurfaces (void)
{
	if (!r_extrasurfaces) return;
	if (!r_numextrasurfaces) return;

	entity_t *lastent = NULL;

	for (int i = 0; i < r_numextrasurfaces; i++)
	{
		extrasurface_t *es = &r_extrasurfaces[i];

		if (es->ent != lastent)
		{
			// draw anything accumulated so far
			R_DrawExtraSurfaces (lastent, es->ent);

			// store back
			lastent = es->ent;
		}

		// accumulate this surface
		if (es->surf->flags & SURF_DRAWSKY)
		{
			R_AddSurfaceToTextureChain (es->surf, &es->ent->model->SkyChain, CHAIN_SKY);
			es->ent->drawflags |= SURF_DRAWSKY;
		}
		else if (es->surf->flags & SURF_DRAWTURB)
		{
			R_AddSurfaceToTextureChain (es->surf, CHAIN_WATER);
			es->ent->drawflags |= SURF_DRAWTURB;
		}
		else if (es->tex->Animated[1])
		{
			R_AddSurfaceToTextureChain (es->surf, CHAIN_LUMA);
			es->ent->drawflags |= SURF_DRAWLUMA;
		}
	}

	// draw anything left over
	R_DrawExtraSurfaces (lastent, NULL);
	r_numextrasurfaces = 0;
}


